package com.lee.study

import com.lee.study.ScriptRunner.runScript
import java.io.File
import javax.script.ScriptEngine
import javax.script.ScriptEngineManager

data class Task(private val name: String) {
    private val preTasks = mutableListOf<Task>()

    fun addPreTasks(preTasks: List<Task>) {
        this.preTasks.addAll(preTasks)
    }

    fun call(taskAlreadyExec: MutableSet<String>) {
        preTasks.forEach { task ->
            task.takeUnless {
                taskAlreadyExec.contains(task.name)
            }?.apply {
                call(taskAlreadyExec)
            }
        }

        println("${name} called")
        taskAlreadyExec.add(name)
    }
}

class TaskMgr {
    private val tasks = hashMapOf<String, Task>()

    fun task(block: () -> Pair<String, List<String>>) {
        val taskParam = block.invoke()
        val tsk = addTask(taskParam.first)
        tsk.addPreTasks(taskParam.second.map { addTask(it) })
    }

    private fun addTask(name: String): Task {
        var task = tasks[name]
        if (task != null) {
            return task
        } else {
            task = Task(name)
            tasks[name] = task
            return task
        }
    }

    fun call(taskName: String) {
        val taskAlreadyExec = mutableSetOf<String>()
        tasks[taskName]?.apply { call(taskAlreadyExec) }
    }
}

fun tasks(block: TaskMgr.() -> Unit) {
    TaskMgr().apply(block)
}

class ErrCollector {
    fun putErr(msg: String) {
        println("putErr ${msg}")
    }

    fun putHint(msg: String) {
        println("putHint ${msg}")
    }
}

object ScriptRunner {
    private fun dynInject(engine: ScriptEngine) {
        engine.eval(
            """
            import kotlin.math.pow
            import com.lee.study.tasks
            infix fun Int.mod(that: Int) = this % that
            infix fun Long.mod(that: Long) = this % that
            infix fun Number.power(n: Int) = this.toDouble().pow(n)
        """.trimIndent()
        )
    }

    fun runScript(scriptFile: File) {
        check(scriptFile.exists()) { "script NOT exists：$scriptFile" }

        // 1. 获取 JSR-223 引擎
        val engine = ScriptEngineManager()
            .getEngineByExtension("kts")!!

        // 通过eval动态插入函数
        dynInject(engine)

        // 通过put插入对象
        val errCollector = ErrCollector()
        engine.put("errCollector", errCollector)

        // 插入顶层函数，只用方法引用或lambda是不行的,方法引用的类型为KFunction，但脚本只能识别
        // Function
//        engine.put("tasks", { block: TaskMgr.() -> Unit -> tasks(block) } as (TaskMgr.() -> Unit) -> Unit)
//        engine.put("add", { x: Int, y: Int -> x + y })

        // 2. 执行脚本
        println("start to run kts：$scriptFile")
        scriptFile.reader().use {
            val res = engine.eval(it)
            println("kts run result:${res}")
        }
    }

}

fun main() {
    val scriptFile = File("src/main/resources/scripts/spel.kts")
    runScript(scriptFile)
}